package sparrowGui.utils
{
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TextEvent;
	import flash.geom.Rectangle;
	import flash.text.TextField;
	
	import sparrowGui.item.Item;
	
	/**
	 * 扩展文本框, 支持表情
	 * 
	 * 	.表情功能:
	 * 
	 * 		.html格式: <disp cls=%s fid=%d data=%d>, 其中
	 * 			cls = 类名, 由 registerClass 中注册, 不指定时, 为默认类.  // 类名格式, 当前仅支持与 AS3 中标示符相同的格式, 即不包含空白字符
	 * 			fid = 帧编号, 当 cls 类指定的是一个 MovieClip 时才有效
	 * 			data = 自定义数据, 必须是一个整数
	 * 
	 * 		.注意: 	所有的参数, 都不包含 单引号/双引号 
	 * 				<disp> 不需要结束符号 </disp> 也不要写成 <disp/>
	 * 				不区分大小写, 各tag间可以有空格
	 * 
	 * 		.最简单的例子: "<disp>"	// 显示默认类的默认帧编号, 不指定 data
	 * 		
	 * 
	 * @author Pelephone
	 */
	public class TextFieldEx extends Sprite {
		public static const ITEM_CLS_UPDATE:String = "item_cls_update";
		
		static public var rep:String			= "";		// 替换文本, 单字符非空格
		static public var rep_reg:RegExp		= new RegExp(rep, "g");
		static public var rep_img_reg:RegExp	= new RegExp("<img />", "g");
		
		private var m_builder:Builder;			// 语法分析器
		private var m_tf:TextField;				// 文本层
		private var m_disp:Sprite;				// 图像层, 提供了表情的显示	
		private var m_cls:Object;				// 图像类, 负责图像资源的注册
		
		//
		static private const DEFAULT_CLASS:String = "default class";
		
		// 建立文本框
		public function TextFieldEx():void{
			
			m_builder = new Builder;
			
			m_tf = new TextField;
			addChild(m_tf);
			m_tf.wordWrap = true;
			m_tf.multiline = true;
			m_tf.addEventListener(TextEvent.TEXT_INPUT, onTextChange, false, 0, true);
			m_tf.addEventListener(Event.SCROLL, onTextChange, false, 0, true);
			
			m_disp = new Sprite;
			addChild(m_disp);
			m_disp.mouseChildren = false;
			m_disp.mouseEnabled = false;
			
			m_cls = new Object;
			
			// 初始化
			this.htmlText = "";
		}
		
		// 当文本滚动/输入时
		private function onTextChange(event:Event):void{
			drawNow();
		}
		
		// 返回指定行的 y 坐标
		private function getLineYPosition(lineIndex:int):Number{
			var ypos:Number = 0;
			for(var i:int=0; i<lineIndex-1; i++){
				ypos += m_tf.getLineMetrics(i).height;
			}
			return ypos;
		}
		
		/**
		 * 注册表情类, 如果是第一个类, 则设置为默认类
		 * @param tClass	类对象, 可以为 Class 或者连接类的 constructor
		 * @param name		类名, 在html代码的 "[disp cls=name]"  中指定类名
		 */
		public function registerClass(tClass:Object, name:String=null):void{
			if(name == null) name = DEFAULT_CLASS;
			m_cls[name] = tClass;
			if(m_cls[DEFAULT_CLASS] == null) m_cls[DEFAULT_CLASS] = tClass;
		}
		
		// 是否已经注册了表情类
		public function hasClass(name:String):Boolean{
			return m_cls[name] != null;
		}
		
		// 返回文本框对象引用
		public function get textField():TextField{
			return m_tf;
		}
		
		// 根据 data 返回显示对象, data 必须是 [disp data=%d] 标签中指定的
		public function getImageReference(data:int):DisplayObject{
			return m_disp.getChildByName(data.toString());
		}
		
		// 宽度
		public override function get width():Number{
			return m_tf.width;
		}
		public override function set width(value:Number):void{
			m_tf.width = value;
			drawNow();
		}
		
		// 高度
		public override function get height():Number{
			return m_tf.height;
		}
		public override function set height(value:Number):void{
			m_tf.height = value;
			drawNow();
		}
		
		//html 内容
		public function get htmlText():String{
			return m_builder.rawText;
		}
		public function set htmlText(text:String):void{
			m_builder.build(text);		// 分析文本
			drawNow();					// 重新显示
		}
		
		// 重新显示文本框, 文本已经建立
		private function drawNow() : void {
			
			// 设置文本
			m_tf.htmlText = m_builder.newText;
			
			// 根据新的排序 item
			m_builder.sortBy( m_tf.text );
			
			// 清楚原来的 disp
			while( m_disp.numChildren ) m_disp.removeChildAt(0);
			
			// 设置滚动偏移,... 
			if(m_tf.scrollV > m_tf.maxScrollV) m_tf.scrollV = 1;
			m_disp.y = -getLineYPosition( m_tf.scrollV );
			
			// 得到字符范围, [char0, char1)
			const line0:int = m_tf.scrollV - 1;				// 0-based
			const char0:int = m_tf.getLineOffset( line0 );	// 0-based
			const line1:int = m_tf.bottomScrollV + 1 - 1;	// next line, 0-based
			const char1:int = (line1 >= m_tf.numLines? 0x7fffffff: m_tf.getLineOffset( line1 ));
			
			var line:int = -1;
			var lineDescent:int;
			
			// 根据 builder 中的每个 item, 建立每个对应的 disp
			for each(var item:Item in m_builder.itemList)
			{
				if(item.indexInText<char0 || item.indexInText>=char1) continue;	// 范围之外, 不显示
				
				// 根据 item.cls 类名建立 disp
				var clsName:String = item.cls;
				if(clsName==null) clsName = DEFAULT_CLASS;		// 使用默认类
				
				var tClass:Object = m_cls[ clsName ];			// 获得构造函数
				if(tClass == null) continue;
				
				var disp:DisplayObject = (new tClass) as DisplayObject;
				if(disp == null) continue;
				
				// 调整行下降, 使排列更优美
//				var curLine:int = m_tf.getLineIndexOfChar(item.indexInText);
//				if(curLine != line){
//					 lineDescent = m_tf.getLineMetrics(curLine).descent;
//					 line = curLine;
//				}
				
				// 得到 disp 对应的范围 border, 并移动到该位置
				if(! moveToChar(disp, item.indexInText, lineDescent, item) ) continue;
				
				// 加上一个把事件托管出去的方法,如果帧了方法就可以对当前对象进行解析了
				// 如果指定了fid, 且 disp 是 MovieClip, 则设置帧号
				if(item.fid!=0 && (disp is MovieClip)){
					(disp as MovieClip).gotoAndStop( item.fid );
				}
				dispatchEvent(new EventEx(ITEM_CLS_UPDATE,[disp,item]));
				
				// 设置 disp 的 data, 保存在 name 属性中
				disp.name = String(item.data);
				
				// add one
				m_disp.addChild( disp );
			}
		}
		
		// 把 disp 移动到字符 charIndex 位置
		private function moveToChar(disp:DisplayObject, charIndex:int, lineDescent:int,itm:Item):Boolean
		{
			var rect:Rectangle = m_tf.getCharBoundaries( charIndex );
			if(rect == null) return false;
			if(!itm || (!itm.width && !itm.height)){
				// 字体的尺寸由 size 决定, 表示绝对像素宽度, 根据该值, 等比例缩放 disp
				disp.width = rect.width;
				disp.scaleY = disp.scaleX;
			}else{
				disp.width = itm.width;
				disp.height = itm.height;
			}
			disp.x = rect.x;
			disp.y = rect.bottom - disp.height - lineDescent;
			return true;
		}
	}
}
import flash.events.Event;

import sparrowGui.item.Item;
import sparrowGui.utils.TextFieldEx;

/**
 * 词法分析器, 从 rawText 中建立 newText, 并获得 item 列表
 */
class Builder{
	
	// <disp>
	static private const re_disp:RegExp 	= /(< *disp.*? *>)/gi;
//	static private const rep:String			= __.k2715;		// 替换文本, 单字符非空格
	static private const rep_reg_r:RegExp	= /\r/g;
	
	//
	private var m_rawText:String;			// 原始文本
	private var m_newText:String;			// 修改后的文本, <disp> 被 rep 代替
	private var m_itemList:Array;			// item列表, 保存了每个图片的信息
	
	
	// 分析指定字符串
	public function build(rawText:String):void{
		
		// 替换 rep 为空格, 因为 rep 作为保留字使用
		rawText = rawText.replace(TextFieldEx.rep_reg, " ");
		rawText = rawText.replace(rep_reg_r, "");			// 替换 "\r" 为 "", 因为 \r 有问题
		
		// 搜索每个项目 Item, 替换为 rep, 并保存到 m_itemList 中
		m_itemList = new Array;
		
		var onCatch:Function = function (match:String,
										 desc:String,
										 index:int,
										 str:String):String
		{
			match; index; str;
			m_itemList.push( new Item( desc ) );
			return TextFieldEx.rep;
//			return "<font size='30'>"+rep+"</font>";
		};
		
		m_rawText = rawText;
		m_newText = rawText.replace(re_disp, onCatch);
	}
	
	// 排列按 text
	public function sortBy(text:String):void{
		
		// 设置每个项目中的 index 属性
		var iItem:int = 0;
		var index:int = -1;
		while(true)
		{
			index = text.indexOf(TextFieldEx.rep, index+1);
			if(index < 0) break;
			
			//
			var item:Item = m_itemList[ iItem++ ] as Item;
			if(item) item.indexInText = index;
		} 
	}
	
	//
	public function get rawText():String{
		return m_rawText;
	}
	public function get newText():String{
		return m_newText;
	}
	public function get itemList():Array{
		return m_itemList;
	}
}

// disp 项目, 负责从 <disp> 描述中得到 cls/fid/data 的属性值, 并保存该 disp 在文本中的 indexInText 索引
class Item{
	
	// cls/fid/data 的正则表达式
//	static private const re_cls:RegExp 	= /cls *= *(?P<cls>\w+)/i;
//	static private const re_fid:RegExp 	= /fid *= *(?P<fid>\d+)/i;
//	static private const re_data:RegExp = /data *= *(?P<data>\d+)/i;
//	static private const re_width:RegExp = /width *= *(?P<width>\d+)/i;
//	static private const re_height:RegExp = /height *= *(?P<height>\d+)/i;
	
	static private const re_cls:RegExp 	= /cls *= *(\'|\")?(?P<cls>\w+)(\'|\")?/i;
	static private const re_fid:RegExp 	= /fid *= *(\'|\")?(?P<fid>\w+)(\'|\")?/i;
	static private const re_data:RegExp = /data *= *(\'|\")?(?P<data>\w+)(\'|\")?/i;
	static private const re_width:RegExp = /width *= *(\'|\")?(?P<width>\w+)(\'|\")?/i;
	static private const re_height:RegExp = /height *= *(\'|\")?(?P<height>\w+)(\'|\")?/i;
	
	// 由 Item() 中建立
	public var cls:String;
	public var fid:int;
	public var data:int;
	
	public var width:int;
	public var height:int;
	
	// 由外部直接设置
	public var indexInText:int;
	
	// 建立 item, 从 text 中分析 cls/fid/data 属性
	public function Item(text:String):void{
		
		// cls
		var ret:Object = re_cls.exec( text );
		cls = (ret==null? null: String(ret[2]));
		
		// fid
		ret = re_fid.exec( text );
		fid = (ret==null? 0: int(ret[2]));			// 1-based, 0=undefined
		
		// data
		ret = re_data.exec( text );
		data = (ret==null? 0: int(ret[2]));
		
		// width
		ret = re_width.exec( text );
		width = (ret==null? 0: int(ret[2]));
		
		// data
		ret = re_height.exec( text );
		height = (ret==null? 0: int(ret[2]));
	}
	
	//
	public function toString():String{
		return "Item(@" + indexInText + "): cls:" + cls + " fid:" + fid + " data:" + data;
	}
}